/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    Process.cpp  
  Description: 

*/

#include "stdafx.h"

using namespace System;
using namespace System::Diagnostics;
using namespace System::Security;
using namespace System::Threading;
using namespace System::Runtime::Serialization;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;

#include "shim_object.h"
#include "shim_process.h"
#include "shim_data.h"
#include "BadResultException.h"
#include "Package.h"
#include "Archiver.h"
#include "GameObject.h"
#include "TimerState.h"
#include "Package.h"
#include "Game.h"
#include "MessageType.h"
#include "Message.h"
#include "Process.h"

using namespace m;
using namespace m::alloc;
using namespace m::io;
using namespace m::text;

namespace SL
{
	namespace M
	{
		Process::Process()
			:
			_nextMessage(nullptr),
			_game(nullptr),
			_HandlingMessage(false),
			_Waiting(false),
			_ProcessEntryDelegate(gcnew ProcessEntryDelegate(this, &Process::ShimProcessEntry)),
			_HandleMessageDelegate(gcnew HandleMessageDelegate(this, &Process::ShimHandleMessage)),
			_peGch(GCHandle::Alloc(_ProcessEntryDelegate)),
			_hmGch(GCHandle::Alloc(_HandleMessageDelegate)),
			_Process(new shim_process())
		{
			_Process->set_process(this);
		}

		Process::~Process()
		{
			Process::!Process();
		}

		Process::!Process()
		{
			if (_Process)
			{
				_Process->release();
			}

			if (_peGch != nullptr)
			{
				_peGch->Free();
			}

			if (_hmGch != nullptr)
			{
				_hmGch->Free();
			}
		}

		void Process::Send(MessageType^ message)
		{
			try
			{
				pin_ptr<const wchar_t> messageChars = PtrToStringChars(message->_type);
				_Process->send(messageChars);
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
		}

		void Process::Send(MessageType^ message, Object ^ data)
		{
			try
			{
				shim_data* shimData = nullptr;
				ref_storage track1;
				if (data != nullptr)
				{
					shimData = create(track1, new shim_data());
					shimData->init(data);
				}
				pin_ptr<const wchar_t> messageChars = PtrToStringChars(message->_type);
				_Process->send(messageChars, shimData);
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
		}

		void Process::Send(MessageType^ message, Object ^ data, bool cancelLast)
		{
			try
			{
				shim_data* shimData = nullptr;
				ref_storage track1;
				if (data != nullptr)
				{
					shimData = create(track1, new shim_data());
					shimData->init(data);
				}
				pin_ptr<const wchar_t> messageChars = PtrToStringChars(message->_type);
				_Process->send(messageChars, shimData, cancelLast);
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
		}

		bool Process::Started::get()
		{
			return _Process->started();
		}

		bool Process::Resumed::get()
		{
			return _Process->resumed();
		}

		bool Process::HasMessage::get()
		{
			return _Process->has_message();
		}

		Game^ Process::Game::get()
		{
			return _game;
		}

		void Process::Game::set(SL::M::Game^ value)
		{
			_game = value;
		}

		Process^ Process::Current::get()
		{
			if (_CurrentProcess)
			{
				if (_CurrentProcess->Started)
				{
					return _CurrentProcess;
				}
			}
			return nullptr;
		}
		
		TimerState^ Process::SetPeriodicTimer(TimeSpan delay, TimeSpan interval, MessageType^ message, Object ^ data)
		{
			try
			{
				TimerState^ ret = gcnew TimerState();
				timer_state* ts = ret->_Ts;
				ref_storage track1;
				shim_data* shimData = nullptr;
				if (data != nullptr)
				{
					shimData = create(track1, new shim_data());
					shimData->init(data);
				}
				pin_ptr<const wchar_t> messageChars = PtrToStringChars(message->_type);
				_Process->set_periodic_timer(&ts, delay.TotalSeconds, interval.TotalSeconds, messageChars, shimData);
				ret->_Ts = ts;
				return ret;
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
		}

		TimerState^ Process::SetPeriodicTimer(TimeSpan delay, TimeSpan interval, MessageType^ message)
		{
			return SetPeriodicTimer(delay, interval, message, nullptr);
		}

		TimerState ^ Process::SetOneShotTimer(TimeSpan delay, MessageType^ message, Object ^ data)
		{
			try
			{
				TimerState^ ret = gcnew TimerState();
				timer_state* ts = ret->_Ts;
				ref_storage track1;
				shim_data* shimData = nullptr;
				if (data != nullptr)
				{
					shimData = create(track1, new shim_data());
					shimData->init(data);
				}
				pin_ptr<const wchar_t> messageChars = PtrToStringChars(message->_type);
				_Process->set_one_shot_timer(&ts, delay.TotalSeconds, messageChars, shimData);
				ret->_Ts = ts;
				return ret;
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
		}

		TimerState ^ Process::SetOneShotTimer(TimeSpan delay, MessageType^ message)
		{
			return SetOneShotTimer(delay, message, nullptr);
		}

		array<byte>^ Process::ReadFile(String ^ fileName)
		{
			Process^ cur = Process::Current;

			if (String::IsNullOrEmpty(fileName))
			{
				throw gcnew ArgumentException("Invalid file name.");
			}

			file_buffer* buffer = new file_buffer();
			try
			{
				pin_ptr<const wchar_t> fileNameChars = PtrToStringChars(fileName);
				cur->Game->_FileStreamer->read_file(buffer, fileNameChars);

				Message^ next = nullptr;
				do
				{
					next = cur->WaitForNextMessage();
				} while (!(next->TypeEquals("us.sl.m.readComplete") && next->NativePtr->data() == buffer));

				size_t size = buffer->size();
				if (size > INT32_MAX)
				{
					throw gcnew IndexOutOfRangeException("Buffer too large.");
				}

				array<byte>^ ret = gcnew array<byte>((int)size);
				pin_ptr<Byte> retPtr = &ret[0];
				buffer->set_offset(0);
				buffer->read((char*)retPtr, (int)size);
				return ret;
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
			finally
			{
				buffer->release();
			}
		}

		String ^ Process::ReadText(String ^ fileName)
		{
			Process^ cur = Process::Current;

			if (String::IsNullOrEmpty(fileName))
			{
				throw gcnew ArgumentException("Invalid file name.");
			}

			file_buffer* buffer = new file_buffer();
			text_buffer* textBuffer = new text_buffer();
			wchar_t* ret = nullptr;
			try
			{
				pin_ptr<const wchar_t> fileNameChars = PtrToStringChars(fileName);
				cur->Game->_FileStreamer->read_file(buffer, fileNameChars);

				Message^ next = nullptr;
				do
				{
					next = cur->WaitForNextMessage();
				} while (!(next->TypeEquals("us.sl.m.readComplete") && next->NativePtr->data() == buffer));

				textBuffer->load(buffer);
				buffer->clear();

				size_t sz = textBuffer->size();
				ret = new wchar_t[sz + 1];
				textBuffer->set_offset(0);
				textBuffer->read(ret, sz + 1);
				ret[sz] = 0;
				return gcnew String(ret);
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
			finally
			{
				buffer->release();
				textBuffer->release();
				if (ret)
				{
					delete[] ret;
				}
			}
		}

		void Process::WriteFile(String ^ fileName, array<byte>^ data)
		{
			Process^ cur = Process::Current;

			if (String::IsNullOrEmpty(fileName))
			{
				throw gcnew ArgumentException("Invalid file name.");
			}

			if (data == nullptr)
			{
				throw gcnew ArgumentNullException("data");
			}

			file_buffer* buffer = new file_buffer();
			try
			{
				pin_ptr<const wchar_t> fileNameChars = PtrToStringChars(fileName);
				pin_ptr<Byte> dataPtr = &data[0];
				buffer->write((char*)dataPtr, data->Length);
				cur->Game->_FileStreamer->write_file(buffer, fileNameChars);

				Message^ next = nullptr;
				do
				{
					next = cur->WaitForNextMessage();
				}
				while (!(next->TypeEquals("us.sl.m.writeComplete") && next->NativePtr->data() == buffer));

				buffer->ThrowIfHr();
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
			finally
			{
				buffer->release();
			}
		}

		void Process::WriteText(String ^ fileName, String ^ data, TextEncoding encoding)
		{
			Process^ cur = Process::Current;

			if (String::IsNullOrEmpty(fileName))
			{
				throw gcnew ArgumentException("Invalid file name.");
			}

			if (data == nullptr)
			{
				throw gcnew ArgumentNullException("data");
			}

			text_buffer* textBuffer = new text_buffer();
			file_buffer* buffer = new file_buffer();
			try
			{
				pin_ptr<const wchar_t> fileNameChars = PtrToStringChars(fileName);
				pin_ptr<const wchar_t> textChars = PtrToStringChars(data);

				textBuffer->write(textChars);
				textBuffer->save(buffer, (m::text::TextEncoding)encoding);
				cur->Game->_FileStreamer->write_file(buffer, fileNameChars);

				Message^ next = nullptr;
				do
				{
					next = cur->WaitForNextMessage();
				} while (!(next->TypeEquals("us.sl.m.writeComplete") && next->NativePtr->data() == buffer));

				buffer->ThrowIfHr();
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}
			finally
			{
				textBuffer->release();
				buffer->release();
			}
		}

		void Process::WriteFile(String ^ fileName, Package ^ package)
		{
			Process^ cur = Process::Current;

			if (String::IsNullOrEmpty(fileName))
			{
				throw gcnew ArgumentException("Invalid file name.");
			}

			if (package == nullptr)
			{
				throw gcnew ArgumentNullException("package");
			}

			try
			{
				pin_ptr<const wchar_t> packageName = PtrToStringChars(fileName);
				package->MakePackage();
				cur->Game->_PackageManager->save(packageName, package->_Package, true);
			}
			catch (bad_result_exception& bre)
			{
				throw gcnew BadResultException(&bre);
			}

			Message^ next = nullptr;
			do
			{
				next = cur->WaitForNextMessage();
			}
			while (!(next->TypeEquals("us.sl.m.savePackageComplete") && next->NativePtr->data() == package->_Package));

			package->ThrowIfBadResult();
		}

		Message^ Process::Wait()
		{
			Process^ cur = Process::Current;
			while (cur->Started)
			{
				Message^ next = cur->WaitForNextMessage();
				if (next->TypeEquals("us.sl.mcore.frame"))
				{
					return next;
				}
			}
			return nullptr;
		}

		Message^ Process::WaitAny(...array<MessageType^>^ messages)
		{
			if (messages == nullptr)
			{
				throw gcnew ArgumentNullException("messages");
			}

			if (messages->Length > 0)
			{
				Process^ cur = Process::Current;
				HashSet<MessageType^>^ messageSet = gcnew HashSet<MessageType^>(messages);

				while (cur->Started)
				{
					Message^ next = cur->WaitForNextMessage();
					if (messageSet->Contains(next->TypeId))
					{
						return next;
					}
				}
			}

			return nullptr;
		}

		Message^ SL::M::Process::WaitAnyFromProcess(Process ^ sender, ...array<MessageType^>^ messages)
		{
			if (messages == nullptr)
			{
				throw gcnew ArgumentNullException("messages");
			}

			Process^ cur = Process::Current;
			HashSet<MessageType^>^ messageSet = gcnew HashSet<MessageType^>(messages);

			while (cur->Started)
			{
				Message^ next = cur->WaitForNextMessage();
				if (sender == next->Sender && messageSet->Contains(next->TypeId))
				{
					return next;
				}
			}

			return nullptr;
		}

		ICollection<Message^>^ SL::M::Process::WaitAll(...array<MessageType^>^ messages)
		{
			if (messages == nullptr)
			{
				throw gcnew ArgumentNullException("messages");
			}

			Process^ cur = Process::Current;
			HashSet<MessageType^>^ messageSet = gcnew HashSet<MessageType^>(messages);
			List<Message^>^ ret = gcnew List<Message^>(messages->Length);

			while (cur->Started && messageSet->Count > 0)
			{
				Message^ next = cur->WaitForNextMessage();
				if (messageSet->Contains(next->TypeId))
				{
					messageSet->Remove(next->TypeId);
					ret->Add(next);
				}
			}

			return ret;
		}

		ICollection<Message^>^ SL::M::Process::WaitAllFromProcess(Process ^ sender, ...array<MessageType^>^ messages)
		{
			if (messages == nullptr)
			{
				throw gcnew ArgumentNullException("messages");
			}

			Process^ cur = Process::Current;
			HashSet<MessageType^>^ messageSet = gcnew HashSet<MessageType^>(messages);
			List<Message^>^ ret = gcnew List<Message^>(messages->Length);

			while (cur->Started &&messageSet->Count > 0)
			{
				Message^ next = cur->WaitForNextMessage();
				if (sender == next->Sender && messageSet->Contains(next->TypeId))
				{
					messageSet->Remove(next->TypeId);
					ret->Add(next);
				}
			}

			return ret;
		}

		Message ^ Process::WaitForNextMessage()
		{
			if (_HandlingMessage)
			{
				throw gcnew InvalidOperationException("Cannot wait for a message from inside a given message handler.");
			}

			_Waiting = true;
			try
			{
				_nextMessage = nullptr;
				do
				{
					NextMessage();
				}
				while (_nextMessage == nullptr);
			}
			finally
			{
				_Waiting = false;
			}
			return _nextMessage;
		}

		bool SL::M::Process::NextMessage()
		{
			if (_HandlingMessage)
			{
				throw gcnew InvalidOperationException("Cannot handle another message from inside a given message handler.");
			}
			return _Process->next_message();
		}

		bool Process::HandleMessage(Message ^ message)
		{
			if (_Waiting)
			{
				_nextMessage = message;
				return true;
			}
			return false;
		}

		void Process::ShimProcessEntry()
		{
			IntPtr exMessageChars = IntPtr::Zero;
			_CurrentProcess = this;

			try
			{
				ProcessEntry();
			}
			catch (OperationCanceledException^ )
			{
				// Not necessarily a bad thing. We handle this here to allow state-driven processes to cancel their operation gracefully.
			}
			// TODO: may want to add more generic exception handlers here.
			catch (BadResultException^ bex)
			{
				if (Debugger::IsAttached)
				{
					OutputDebugString(L"Unhandled BadResultException here.\n You may want to get the stack trace before we continue to unwind.");
					Debugger::Break();
				}

				exMessageChars = Marshal::StringToHGlobalAnsi(bex->Message);
				throw bad_result_exception(bex->ErrorCode, (char*)exMessageChars.ToPointer());
			}
			finally
			{
				if (exMessageChars != IntPtr::Zero)
				{
					Marshal::FreeHGlobal(exMessageChars);
				}
				_CurrentProcess = nullptr;
			}
		}

		bool Process::ShimHandleMessage(m::message * messagePointer)
		{
			try
			{
				_HandlingMessage = true;
				return HandleMessage(gcnew Message(messagePointer));
			}
			finally
			{
				_HandlingMessage = false;
			}
		}
	}
}